package service

import (
	"context"
	"encoding/json"
	"time"

	"wxpay/internal/conf"
	"wxpay/internal/dao"
	"wxpay/internal/dto"
	"wxpay/internal/model"
	"wxpay/pkg/merrors"
	"wxpay/pkg/util"

	"gorm.io/gorm"

	mchApi "wxpay/pkg/wechat_api"
)

type refundService struct {
	d dao.DataStore
}

func NewRefundService(d dao.DataStore) *refundService {
	return &refundService{d: d}
}

// 退款申请
func (s *refundService) RefundApply(ctx context.Context, refundDto *dto.RefundApplyDTO) (err error) {
	const op merrors.Op = "refundService.RefundApply"
	// 构造交易对象
	trade, err := s.d.Trade().QueryByOutTradeNo(ctx, s.d.GetTx(ctx), refundDto.OutTradeNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 交易不存在
		return merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "交易不存在")
	}
	if err != nil {
		return
	}
	if !trade.DoesBelongTo(refundDto.BrandID) {
		return merrors.E(op, merrors.Permission, "无权查看")
	}
	// 根据订单信息构造dto
	tradeAmount := trade.GetAmountInfo()
	refundDto.RefundAmount.Total = int64(tradeAmount.Total)
	refundDto.RefundAmount.PayerTotal = int64(tradeAmount.PayerTotal)
	refundDto.MchID = trade.MchID
	refundDto.SubMchID = trade.SubMchID
	refundDto.AppID = trade.AppID
	refundDto.SubAppID = util.ConvertNullString(trade.SubAppID)
	refundDto.WxTransID = util.ConvertNullString(trade.WxTransID)

	client, err := NewMchService(s.d).BuildMchApiClient(ctx, trade.MchID)
	if err != nil {
		return merrors.E(op, merrors.Invalid, err)
	}
	// 构造微信api请求参数
	req := mchApi.RefundRequest{
		SubMchID:    refundDto.SubMchID,
		SpAppID:     refundDto.AppID,
		SubAppID:    refundDto.SubAppID,
		OutTradeNo:  refundDto.OutTradeNo,
		OutRefundNo: refundDto.OutRefundNo,
		Reason:      refundDto.Reason,
		Amount: struct {
			Refund   uint   `json:"refund"`
			Total    uint   `json:"total"`
			Currency string `json:"currency"`
		}{Refund: uint(refundDto.RefundAmount.Refund),
			Total:    uint(refundDto.RefundAmount.Total),
			Currency: refundDto.RefundAmount.Currency},
		NotifyUrl:    conf.Conf.Callback.Endpoint + "/api/v1/wxpay/notify",
		FundsAccount: refundDto.FundsAccount,
	}
	resp, err := client.RefundApply(ctx, req)
	if err != nil {
		return merrors.E(op, merrors.Invalid, merrors.ErrWxApi, err)
	}
	refundDto.RefundID = resp.RefundID
	refund := dto.AdaptRefund(refundDto)
	if err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		if e := s.d.Refund().Create(ctx, tx, &refund); e != nil {
			return e
		}
		// 创建定时任务，兜底查询
		if e := s.createRefundQueryTask(ctx, tx, &refund); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	return nil
}

// 退款查询同步任务
func (s *refundService) RefundSync(ctx context.Context, outRefundNo string) (err error) {
	const op merrors.Op = "refundService.RefundSync"
	rf, err := s.d.Refund().QueryByOutRefundNo(ctx, s.d.GetTx(ctx), outRefundNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 退款不存在
		err = merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "退款单不存在")
		return
	}
	if err != nil {
		return
	}
	if rf.IsFinalState() {
		// 已经是最终状态，直接返回
		return nil
	}
	// 查询微信退款单状态
	query := mchApi.QueryRefundByOutRefundNoRequest{
		SubMchID:    rf.SubMchID,
		OutRefundNo: rf.OutRefundNo,
	}
	client, err := NewMchService(s.d).BuildMchApiClient(ctx, rf.MchID)
	if err != nil {
		return
	}
	resp, err := client.QueryRefundByOutRefundNo(ctx, query)
	if err != nil {
		err = merrors.E(op, merrors.Invalid, merrors.ErrWxApi, "微信支付查询退款失败")
		return
	}
	notify := &dto.RefundNotificationDTO{
		SpMchID:             rf.MchID,
		SubMchID:            rf.SubMchID,
		OutTradeNo:          rf.OutTradeNo,
		TransactionID:       rf.WxTransID,
		OutRefundNo:         rf.OutRefundNo,
		RefundID:            resp.RefundID,
		RefundStatus:        resp.Status,
		SuccessTime:         resp.SuccessTime,
		UserReceivedAccount: resp.UserReceivedAccount,
		Amount:              dto.RefundNotifyAmount{PayerRefund: resp.Amount.PayerRefund},
	}
	err = s.doSync(ctx, rf, notify)
	return
}

// 退款通知更新
func (s *refundService) RefundNotify(ctx context.Context, notify *dto.RefundNotificationDTO) (err error) {
	const op merrors.Op = "refundService.RefundApply"
	// 构造交易对象
	refund, err := s.d.Refund().QueryByOutRefundNo(ctx, s.d.GetTx(ctx), notify.OutRefundNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 退款单不存在
		err = merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "退款单不存在")
		return
	}
	if err != nil {
		return
	}
	if refund.IsFinalState() {
		// 已经是最终状态，不需要更新，直接返回
		return
	}
	err = s.doSync(ctx, refund, notify)
	return
}

// 处理各种退款状态
func (s *refundService) doSync(ctx context.Context, refund *model.Refund, notify *dto.RefundNotificationDTO) (err error) {
	// 更新退款单状态
	switch notify.RefundStatus {
	case model.RefundStatusSuccess:
		err = s.success(ctx, refund, notify)
	case model.RefundStatusAbnormal:
		err = s.abnormal(ctx, refund, notify)
	case model.RefundStatusClosed:
		err = s.close(ctx, refund, notify)
	case model.RefundStatusProcessing:
		err = s.processing(ctx, refund, notify)
	}
	if err != nil {
		return
	}
	return
}

// 退款成功
func (s *refundService) success(ctx context.Context, refund *model.Refund, notify *dto.RefundNotificationDTO) (err error) {
	sT, err := util.ParseTime(util.LayoutSecondWithTimeZoneAndT, notify.SuccessTime)
	if err != nil {
		return
	}
	if err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		// 获取锁
		t, e := s.d.Trade().QueryByOutTradeNo(ctx, tx, refund.OutTradeNo)
		if e != nil {
			return e
		}
		// 因为计算退回手续费需要交易金额和交易手续费，所以需要锁交易记录
		tr, e := s.d.Lock().LockTrade(ctx, tx, t.ID)
		if e != nil {
			return e
		}
		// 查询本次退款单
		m, e := s.d.Refund().QueryByOutRefundNo(ctx, tx, refund.OutRefundNo)
		if e != nil {
			return e
		}
		// 查询交易相关的已成功退款单
		refunds, e := s.d.Refund().QueryRefundedByOutTradeNo(ctx, tx, m.OutTradeNo)
		if e != nil {
			return e
		}

		// 更新model
		rfAmount := model.RefundAmount{PayerRefund: notify.Amount.PayerRefund}
		if e := m.UpdateToSuccess(sT, notify.UserReceivedAccount, rfAmount, tr, refunds); e != nil {
			return e
		}
		// 持久化
		if e := s.d.Refund().Success(ctx, tx, m); e != nil {
			return e
		}
		// 保存消息
		if e := s.createRefundNotification(ctx, tx, m); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return
	}
	return
}

func (s *refundService) createRefundNotification(ctx context.Context, tx *gorm.DB, r *model.Refund) (err error) {
	payload := model.NewRefundNotification(r)
	ps, _ := json.Marshal(&payload)
	msg := model.NewMessage(model.TopicRefundNotify, ps, 0)
	if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
		return e
	}
	return nil
}

// 退款异常
func (s *refundService) abnormal(ctx context.Context, refund *model.Refund, notify *dto.RefundNotificationDTO) (err error) {
	if err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		// 获取锁
		m, e := s.d.Lock().LockRefund(ctx, tx, refund.ID)
		if e != nil {
			return e
		}
		// 更新model
		if err = m.UpdateToAbnormal(notify.UserReceivedAccount); err != nil {
			return err
		}
		// 持久化
		if e := s.d.Refund().Abnormal(ctx, tx, m); e != nil {
			return e
		}
		// 保存消息
		if e := s.createRefundNotification(ctx, tx, m); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return
	}
	return
}

// 退款关闭
func (s *refundService) close(ctx context.Context, refund *model.Refund, notify *dto.RefundNotificationDTO) (err error) {
	if err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		// 获取锁
		m, e := s.d.Lock().LockRefund(ctx, tx, refund.ID)
		if e != nil {
			return e
		}
		// 更新model
		if e = m.UpdateToClosed(notify.UserReceivedAccount); e != nil {
			return e
		}

		if e := s.d.Refund().Close(ctx, tx, m); e != nil {
			return e
		}
		// 保存消息
		if e := s.createRefundNotification(ctx, tx, m); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return
	}
	return
}

// 退款处理中
func (s *refundService) processing(ctx context.Context, refund *model.Refund, notify *dto.RefundNotificationDTO) (err error) {
	if err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		// 获取锁
		m, e := s.d.Lock().LockRefund(ctx, tx, refund.ID)
		if e != nil {
			return e
		}
		// 更新model
		if e = m.UpdateToProcessing(notify.UserReceivedAccount); e != nil {
			return e
		}

		if e := s.d.Refund().Processing(ctx, tx, m); e != nil {
			return e
		}
		// 创建定时任务 -- 继续查询
		if e := s.createRefundQueryTask(ctx, tx, m); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return
	}
	return
}

func (s *refundService) createRefundQueryTask(ctx context.Context, tx *gorm.DB, r *model.Refund) (err error) {
	payload := model.NewRefundQueryTask(r)
	ps, _ := json.Marshal(&payload)
	msg := model.NewMessage(model.TopicRefundQuery, ps, 10*time.Minute)
	if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
		return e
	}
	return nil
}
